fix(providers): Copilot mixed-wire routing, DeepSeek service_tier + reasoning replay, Claude 4.6/4.7 1M windows - #889
fix(providers): Copilot mixed-wire routing, DeepSeek service_tier + reasoning replay, Claude 4.6/4.7 1M windows#889lidge-jun wants to merge 28 commits into
Conversation
…dence Fourteen documents: the ask decomposed, four read-only research lanes against upstream 2b5bdcf67 and dev f9b9440, a UX design, seven implementation phases, and a deferred Theme stub. Two findings reshape the build. model_instructions_file replaces the entire base prompt rather than adding to it, so custom layers compose into developer_instructions instead. And the Logs tab the ask names as the model uses exclusive hash-persisted tabpanels, not the SectionTabs scroll-spy strip that shares the name. The hard constraint - layers that must never be disableable - is now a proven set rather than a guess: base/model instructions, model-switch, AGENTS.md, realtime, plugins, and non-Skills extension layers have no include_ key anywhere in the schema.
…eference) Survey unit for applying client integrations as an on/off switch from the management API: cc-switch additive/exclusive toggle mechanics, per-client requirements for Hermes/OpenClaw/Kimi Code/Gajae Code, and design options with a five-state read-back model and risk register. Research only; no production code.
An independent audit returned FAIL on twelve counts. The central architecture survived - model_instructions_file replaces the base prompt, so custom layers belong in developer_instructions - but the persistence protocol would have corrupted user config. Fencing layer bodies inside a TOML multiline string was the worst of it. A body containing triple quotes terminates the value and a backslash is an escape, so arbitrary prose could produce a file Codex cannot parse at all. The sidecar JSON made it worse by requiring two-way reconciliation that no rule actually defined. Storage is now one owned JSON file as the single source of truth, with developer_instructions as a generated projection written through a real TOML serializer and verified by a reparse. No fences, so no text can collide with a delimiter. Revision hashes and a write-order contract close the concurrency hole that atomic rename never covered. The layer inventory was also wrong: it listed Plugins as impossible to disable while session/mod.rs checks Feature::Plugins. A single cannot-be-turned-off list is replaced by five explicit classes, exported once from WP1 and consumed by route and GUI alike, with a partition test. The API derives its refusals from that inventory rather than a hand-maintained deny-list. Two overclaims are retracted. effective becomes defaultedUserValue because opencodex reads one config layer out of eight. The linter's size rule drops its citation of the 32 KiB AGENTS.md budget, which governs project-doc loading and never constrained developer_instructions. Phases re-sliced to remove four forward dependencies, and the missing-config state no longer disables the switch that was supposed to create the file.
Round 2 of the audit rejected the smol-toml plan on six counts. A live probe on this machine settled it harder than the audit could: Bun 1.3.14's TOML.parse transposes \t and \f, rejects \u0007, and does not trim the newline after an opening triple quote. Codex parses with Rust toml_edit. So a verify-by-reparse design would have checked our encoding against a parser with known-transposed escapes and reported success on a file Codex might read differently. The fix is to stop needing a parser. Bodies accept printable Unicode, spaces and newlines; tabs normalize to four spaces and CRLF to LF; control characters are refused. Within that set the escaping is three unambiguous rules, and verification becomes a byte comparison. No production dependency is added, which also retires the unreviewed dependency the audit flagged - it was BSD-3-Clause rather than MIT, absent from the lockfile, and its parse() never exposed the source spans the plan claimed. The transaction is rebuilt around a journal. config.toml is written first now, so a failed request leaves the source of truth untouched rather than committing JSON and returning an error. Reads never write: drift is reported as state and resolved by an explicit POST, because an HTTP GET must not modify a user's config. A missing store with a live owned projection is no longer treated as an empty store - that would have erased the active prompt on the next write. Refusing an externally authored developer_instructions is no longer a dead end: an adopt flow shows the raw line, offers a copy, and imports it on confirmation. Also corrected: the assembly table now uses the five-class vocabulary instead of contradicting section 4, two citations the audit judged overstated are narrowed to what the source actually shows, and 003 no longer mandates an effective field that 010 and 005 deliberately defer.
Round 3 confirmed the Bun.TOML measurements independently and accepted the restricted encoder, then found the recovery algorithm destructive. The rule was 'if either target differs from the post-image, rewrite both'. Crash after the first write, let the user or Codex edit config.toml, and recovery overwrites their edit with a stale post-image. Recovery now classifies each target against both recorded hashes and refuses to write any file matching neither - that file belongs to someone else. One unrecognised target aborts the whole recovery with recovery_required rather than rolling forward into a state nobody intended. The commit point was also mixing two models: journal-rename-is-commit alongside a rollback that claimed a failed request changes nothing. The journal is now prepared intent, commit is both targets matching the post-image, and rollback is the only failure path. Durability adds parent-directory fsync with a documented Windows fallback. Stale-lock breaking could delete a successor's lock and admit two writers. Takeover is now an atomic rename to a token-quarantined name that exactly one contender can win, and release deletes only a lock whose token still matches. Adoption imported the raw TOML source line as the body, so a value containing an escaped newline would have become twelve literal characters. It now decodes through the inverse of our own encoder, accepting only the three escapes we emit, and previews both the source line and the decoded body. Malformed marker-owned lines get the same flow instead of being a permanent lockout, and store-missing repair is described as what it is: salvage of one concatenated string, with the losses listed and a backup written first. Character validation is defined over Unicode scalar values, rejects unpaired surrogates and C1 controls, measures caps in UTF-8 bytes after normalization, and reports code-point positions. One golden fixture parsed by real toml_edit now backs the claim that byte equality means Rust reads what we intended.
Campaign preparation (docs-only): five units under devlog/_plan/260802_wtN_* with 000 research + 010 implementation roadmaps, claim ledgers verified by a lunasearch fan-out (Anthropic 1M windows, Copilot mixed-wire, DeepSeek service_tier, WHATWG extension origins, POSIX rename-over-symlink). wt1 update-path: PR #871, issue #879 (star-prompt deferral leakage), #557 optional wt2 zero-leak: PRs #840 #841 #843 #844 #845 #847 (tracker #820) wt3 provider-wire: PRs #746 #860 #839/#854, issue #875 triage, #616/#837 optional wt4 server-config: PRs #850 (CORS origin confusion), #869 (symlink destruction) wt5 windows-service: PRs #868, #861 (issue #848)
…tract Round 4 found the transaction still holding both models at once: journal existence was called the commit point while rollback claimed a failed request changes nothing. The journal is now unambiguously prepared intent - commit is deleting it after both targets verify against the post-image - so a journal found on disk always means the transaction never committed, and recovery rolls back. The one exception is not a roll-forward: when both targets already match the post-image the writes had finished and only the commit step was missing. An unparseable journal previously implied restoring a pre-image that lives inside the same damaged document. The journal is now a checksummed envelope and any failure of that checksum is recovery_required with nothing read and nothing written. The pre-rename guard only rehashed config.toml, leaving the store overwritable by a third party between compare and write; each target now re-verifies its own bytes immediately before its own rename, and rollback applies the same pre/post/neither classification. Post-write verification compares complete bytes instead of looking for our two lines, so another writer changing an unrelated key can no longer be reported as success. WP1's exported contract was missing drift, adopt, repair, salvage, and recovery entirely, so WP2 could not have been built from it. All are declared now, with read-only preview DTOs, and every filesystem mutation stays in WP1. Modes were specified for config.toml alone. The store, journal, salvage backup, and lock all carry prompt bodies or metadata and are now 0600 at creation rather than by later chmod. Salvage backups use exclusive creation with a random suffix and abort the operation if they cannot be made durable. Also: the 001 assembly table fix from the previous round never landed on that block - the audit was right that the committed file still carried ALWAYS-ON. It is corrected here, along with the last locked/features array references.
GO-WITH-FIXES (5 blockers) all folded: CORS fix retargeted to src/server/auth-cors.ts isExtraAllowedOrigin with full origin_rejected coverage instruction; ANTHROPIC_MODEL_CONTEXT_WINDOWS correctly assigned to registry.ts:217; wt2/wt3 shared-file coordination named in both units; atomicWriteFile caller audit switched to grep-at-P with oauth/store.ts named explicitly.
…ds on
Round 5 opened core/src/mcp.rs and disproved two of my drafts at once:
let plugins_available =
selected_plugin_available || !loaded_plugins.capability_summaries().is_empty();
Feature::Plugins feeds plugins_config_input and therefore the right operand,
but selected_plugin_available is an independent OR path that can make the
section emit regardless. The flag influences emission; it does not gate it. The
citation both earlier drafts leaned on, session/mod.rs:3422-3430, gates
recommended plugin candidates rather than this section. Plugins is now
runtime-conditional in the table, in class D, and out of the feature list, with
the residual question recorded as UNKNOWN rather than asserted.
Three other corrections. A stale paragraph still told recovery to restore from
the pre-image of a journal that had failed its own checksum - exactly the
impossible path the envelope section rejects - and it is deleted. Adoption now
states its five ordered steps and previews the post-normalization body, because
previewing decoded text while committing normalized text would show one string
and save another. WP2's repair contract gains owned-malformed, which it omitted
entirely, and stops calling store recovery reconstruction when 010 defines it as
lossy salvage.
Also enumerated the lock interleaving the audit asked for - A quarantines, B
acquires, A's wx fails and A retries without touching B's lock - and made
Windows write-through an explicit WP1 acceptance gate rather than an assumption:
establish what Bun exposes, or fail closed as recovery_required with a test on
that branch.
The stale UX paragraph promised a built-in dialog showing rendered prompt text and an effective value. Neither exists: Codex exposes no API for rendered layer bodies, and opencodex reads one config layer out of eight. 005 now says what the dialog actually shows and names the two things it does not. SalvagePreview returned a backupPath, which either reserves a name during a read-only preview or promises one that exclusive creation may refuse at commit. It returns backupDir now; the real path is created during the confirmed mutation and comes back in the WriteResult. Adoption tests 26a-26g enumerate the pipeline the prose already required: normalization, control rejection with position, post-normalization overflow, composed overflow, and the property that matters most - the previewed body is byte-identical to the committed one. The Windows durability gate stays open by design. It is an acceptance criterion for WP1, not something a document can settle.
…ollback UX design spec for merging the API/Claude/Grok pages into one Integrations tab: hash-routed sub-tabs, ops-hero with install detection and capability- aware switches, per-client settings, and a two-level rollback contract (operation journal + restore preflight). Design only; no components.
First slice of WP1: the canonical layer inventory plus the encoding contract. No file writes yet. The encoder exists because Bun.TOML cannot verify our work. Measured on 1.3.14, its parser transposes \t and \f, rejects \u0007, and does not trim the newline after an opening triple quote. Codex parses with Rust toml_edit, so a verify-by-reparse loop could report success on a file Codex reads differently. So bodies are restricted instead: tabs become four spaces, CRLF becomes LF, and control characters, DEL, C1, and unpaired surrogates are refused with a code-point position. Within that set the escaping is three total rules and the emitted line is checked against a hand-written grammar that shares no code with the encoder. The decoder is deliberately narrow - it accepts only the three escapes we emit and refuses \t, \f, \b, \r and \uXXXX rather than guessing. The inventory classifies every layer into exactly one of five classes, and the tests assert the partition is total and disjoint so the API can derive switchability from it instead of a hand-maintained deny-list. Plugins is runtime-conditional: core/src/mcp.rs:200 ORs selected_plugin_available with the loaded summaries, so [features] plugins feeds only the right operand. The surrogate guard was driven red once to prove the test is not vacuous.
The scanner flags concrete home paths, and three example snippets carried one. Nothing sensitive was in them, but the gate is the gate.
…d mechanisms wp-a: modelWireDefaults on the github-copilot registry entry (mechanism already on tree: hard pin > modelAdapters > registry defaults > provider adapter); conservative 6-model set, nano/sol lead-only via modelAdapters. wp-b: adopt PR #860's capability file map + #875 root cause found — sanitizeReasoningInputContent blanks plaintext reasoning for every Responses provider (openai-responses.ts:35, called :1027); scope it. wp-c: registry.ts:217 map + authoritative-window [1m] predicate.
1: docs target corrected to reference/configuration/providers.md + locales 2: exact seed before/after array, defaultModel unchanged, nano/sol excluded 3: evidence table moved to 000 (LEXICO-SPLIT-01) 4: per-model pi.dev provenance + two-leg selection rule (sol/nano out)
…ve seed policy 1: gpt-5.6-sol meets the two-leg rule identically to luna/terra (#748 field report + pi.dev Responses declaration + JetBrains conditional chat failure); excluding it was an inconsistent rule application. nano stays out (no field report leg). 2: seed policy made explicitly additive (no removals); gpt-5-mini added as a verified chat model so the chat regression fixture is honest; scenario 4 corrected.
…ecedence fixture claim ledger and selection reference now say seven built-in / sol verified; scenario 3 uses gpt-5.4-nano or gpt-5-mini for the opt-in direction since sol is itself a default and cannot prove override precedence.
github-copilot fronts a mixed-wire catalog: gpt-5.3-codex, gpt-5.4, gpt-5.4-mini, gpt-5.5, gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra reject /chat/completions for real Codex-agent traffic (function tools + reasoning). Declare them as registry modelWireDefaults so every inbound wire rides Responses while the provider-wide adapter stays openai-chat for the chat-served catalog. Explicit modelAdapters still win in both directions; gpt-5.4-nano stays out (no field report) as a documented override example. Evidence: issue #748 field runs, pi.dev wire declarations, litellm#23332 (gpt-5.4), JetBrains LLM-29711 (gpt-5.6-sol). Tests: tests/github-copilot-wire-defaults.test.ts — 22 cases incl. captured-upstream-URL replay proof on all three inbound wires. Consolidates the routing half of PR #746 (its sampling/credential-replay half remains a separate parity/security unit). Closes #748.
…s escape hatch reference/configuration/providers.md (EN + ko/ja/zh-cn/ru): the modelAdapters row now names the built-in Copilot Responses-only defaults and the opt-in path for models without one (gpt-5.4-nano example). guides/providers.md gains the routing-precedence note (pin > modelAdapters > registry default > provider adapter).
…ntics adopts #860's reviewed fail-closed semantics (undefined strips; explicit config is the escape hatch); names preserveResponsesReasoningContent as the reasoning-replay flag following the statelessResponses flow; maps every edit point on the current tree.
1: research survey moved to 000 (LEXICO-SPLIT-01) 2: guides/codex-app-models.md x5 locales added to docs file map 3: options.serviceTier clearing required + tested 4: ocxr1-strip-under-preservation negative case added 5: comment wording calibrated (accepted replay = verified; required = inference)
…epSeek reasoning replay Two DeepSeek wire fixes, one capability flow: 1. service_tier is an OpenAI-only Responses parameter, but fast mode injected it for every Responses provider. A provider-level supportsServiceTier capability now gates it after the final route is settled: canonical openai/openai-apikey keep fast-mode inject/remove (unset fast mode preserves a caller value); deepseek and volcengine-agent-plan strip it; unclassified providers fail closed unless explicitly opted in. Stripping also clears options.serviceTier so logging never mislabels a removed tier. Adopts PR #860's reviewed fail-closed semantics. 2. sanitizeReasoningInputContent blanked reasoning content for EVERY Responses provider — a rule only the ChatGPT native backend needs. DeepSeek's Responses API accepts plaintext reasoning replay, so providers flagged preserveResponsesReasoningContent keep it (ocxr1 envelopes are still stripped). Fixes the local half of #875: continuations after tool calls no longer reach DeepSeek with emptied reasoning items. Both fields flow registry -> providerConfigSeed -> enrichProviderFromRegistry -> router backfill without overriding explicit config. Tests: tests/service-tier-capability.test.ts + tests/deepseek-reasoning-replay.test.ts (18 cases incl. live handleResponses payload capture).
…ent reference rows, capability-gated fast-tier guides reference/configuration/providers.md (EN/ko/ja/zh-cn/ru): rows for the two new provider fields. guides/codex-app-models.md (all five locales): the blanket 'routed non-OpenAI models strip service-tier metadata' wording is now the capability-gated fail-closed behavior with the explicit opt-in — closing #860's open docs review issue.
Seeding them via providerConfigSeed broke the management API's canonical openai seed comparison (exact key set) — 4 management-provider-validation failures. Follow the modelWireDefaults philosophy instead: the registry holds the defaults, providerConfigSeed stays free of them so an explicit user value stays distinguishable, and enrichProviderFromRegistry + the router backfill supply them from the entry directly.
…ts-inject + generator override desktop-profile and model-info guards already landed; the open halves are the registry map, #854's withSubagentContextMarker port, and the stale jawcode generator override contradicting its own committed output.
…ts + full regression set my stale check misread the bedrock rows: the anthropic section of the generated metadata still pins sonnet-4-6 at 200k with a catalog test blessing it; file map now includes the generated rows, codex-catalog test, registry-parity assertions, and #854's complete test set (modified 372k test + five additions incl incomplete-metadata branch).
…tative [1m] in generated profiles Two halves, consolidated from PRs #839 and #854: 1. ANTHROPIC_MODEL_CONTEXT_WINDOWS omitted claude-opus-4-7, claude-opus-4-6, and claude-sonnet-4-6 although all three ship in ANTHROPIC_MODELS — they advertised max_input_tokens null, emitted no [1m] picker row, and Claude Code accounted them at its 200k default. All three are documented at 1M by Anthropic (Opus 4.6 2026-02-05, Opus 4.7 2026-04-16, Sonnet 4.6 2026-02-17). The generated jawcode metadata and its 200k-pinned catalog test move with the registry; the stale CONTEXT_WINDOW_OVERRIDES generator pin is removed. 2. Generated subagent defs marked [1m] with the MAIN-SESSION auto-context predicate, so a 372K route was written [1m] into generated profiles — accounted at 1M with no compaction pairing in the subagent. Generated defs now mark only authoritative >=1M windows, strip inherited unsafe markers to bare, preserve genuine routed [1m] ids (kimi/k3[1m]) and provider caps, and keep selectors with unknown windows as-was. Main-session env-slot marking is unchanged. Tests: 372k roster test corrected + five new regressions (catalog-derived 1M markers, routed [1m] preservation, 350K cap unmarking, marker-case precedence, incomplete-metadata preservation), catalog 1M contract, registry parity assertions for all three windows.
📝 WalkthroughWalkthroughThis PR adds research and implementation plans for client toggles and Codex Set prompt composition. It also updates provider capability handling, GitHub Copilot wire routing, Responses reasoning replay, Claude context markers, related tests, and localized documentation. ChangesClient toggle planning
Codex Set prompt composer
Provider behavior
Worktree plans
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 34
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@devlog/_plan/260802_client_toggle_api/000_plan.md`:
- Around line 41-48: Update Q1 in the cc-switch reference mechanics section to
distinguish cc-switch’s provider store at ~/.cc-switch/cc-switch.db, device
settings in settings.json, and backup data under backups/. Replace the current
ownership reference to ~/.cc-switch/config.json and clarify which state
cc-switch owns versus which client files it mutates.
In `@devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md`:
- Around line 3-13: Pin all cited research sources to immutable revisions: in
devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md:3-13,
replace every cc-switch blob/main link with blob/v3.17.0; in
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md:25-27 and
:56-58, identify the exact Hermes Agent and OpenClaw commits used on 2026-08-02
and link each citation to its commit, without substituting current branch heads.
- Around line 32-34: The client classification at the referenced statement
incorrectly groups all four clients as additive-class. Keep Hermes and OpenClaw
classified as additive, classify Kimi Code separately because disabling removes
related provider, alias, and default entries, and mark Gajae Code as
candidate—unverified; require a fixture proving disable preserves unrelated
models.yml configuration before applying the additive writer contract.
In `@devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md`:
- Around line 19-21: Make vendor integration paths conditional in the
client-toggle design. In
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md lines 19-21
and 38-43, describe Hermes dashboard access as raw-config only rather than
provider CRUD; in lines 107-119 and 172-176, mark the Kimi registry
import/removal path conditional on the undocumented opencodex api.json contract.
In devlog/_plan/260802_client_toggle_api/003_api_design_options.md lines 166-171
and 192, apply the same conditional Kimi treatment, and link lines 214-215 from
every dependent section.
In `@devlog/_plan/260802_client_toggle_api/003_api_design_options.md`:
- Around line 9-19: Replace the machine-local /Users/jun/Developer/... links in
devlog/_plan/260802_client_toggle_api/003_api_design_options.md lines 9-19 and
devlog/_plan/260802_client_toggle_api/004_ux_design.md lines 4-12 with
repository-relative links or immutable repository URLs, preserving the
referenced symbols and destinations.
- Around line 186-197: Apply the §3 provenance transaction to each vendor-CLI
mutation in the per-client enable/disable flow: capture the canonical pre-write
fingerprint, reject existing opencodex entries lacking ownership proof, invoke
the CLI, then reread and validate the canonical file. Recheck the fingerprint
before persisting ownership metadata and the post-write hash; if metadata
persistence fails, roll back the CLI change or report unsafe/conflict, never
success, so current, stale, and conflict remain derivable.
In `@devlog/_plan/260802_client_toggle_api/004_ux_design.md`:
- Around line 252-265: Update the credential controls in the Settings design to
be capability-aware: hide generic environment-variable and export/download
controls for clients that do not support them, and provide Kimi Code with an
explicit manual-key flow or placeholder because it requires a key for
non-loopback use. Ensure raw configuration previews are sanitized before
rendering or returning them by redacting API keys, OAuth material, and other
sensitive credential fields.
- Around line 287-301: Extend the “Operation journal and snapshot identity”
section to define security for retained full-file snapshots: require restrictive
file permissions and ACL hardening, state whether at-rest encryption is
mandatory, prohibit snapshot contents and secrets in status/history responses
and logs, and specify failure handling for bounded snapshot cleanup. Preserve
exact-byte restore semantics by protecting complete snapshots rather than
redacting their contents.
- Around line 207-224: Resolve the OpenCode toggle scope before exposing its
controls: reconcile the runtime injection behavior in the opencode launcher with
the file-toggle model described in the UX design. Either remove OpenCode from
the v1 five-state, switch, and snapshot-restore client set, or explicitly define
the switch as disk-only while modeling direct opencode runtime state separately,
then align the API and 003_api_design_options.md §4 accordingly.
In `@devlog/_plan/260802_codex_set_prompt_composer/005_ux_design.md`:
- Around line 8-13: Add a text or plaintext language identifier to both fenced
UI example blocks in the documentation, including the block containing “Codex
Set” and the additional block around the referenced section, so all fences
satisfy MD040 without changing their contents.
In `@devlog/_plan/260802_codex_set_prompt_composer/010_wp1_prompt_layers_core.md`:
- Around line 250-262: Remove the obsolete opencodex-prompt.md and
model_instructions_file design from the WP1 plan, including the stale branch
around the model_instructions_file discussion. Keep one final contract
throughout: opencodex-prompt.json is the source of truth for all custom layers,
developer_instructions is the generated config.toml projection of enabled
layers, and model_instructions_file is never written.
- Around line 472-483: Replace the textual concatenation used to compute
revision with unambiguous length-prefixed binary framing that encodes each
file’s label, existence flag, byte length, and complete bytes in a fixed order.
Update the revision-generation logic described in the `revision` section, and
add a regression test covering the shown newline ambiguity so distinct
config/store contents produce distinct revisions.
- Around line 50-64: Extend PromptLayerSnapshot with an explicit configMalformed
status or Drift value and add the corresponding config_malformed variant to
WriteError. Propagate malformed config.toml detection through the snapshot and
route layers so GET reports the distinct state and every mutation refuses writes
with config_malformed, then add coverage for GET and all mutation paths.
- Around line 559-574: Define the journal envelope fields preConfig, postConfig,
preStore, and postStore as base64-encoded file bytes, and specify the canonical
serialization of the complete line 2..n payload used for the SHA-256 checksum.
During recovery, verify the checksum and validate/decode every encoded field
before parsing or writing; reject invalid or truncated journals as
recovery_required.
- Around line 104-108: Update WP1 to export a pure custom-layer
normalization/validation helper, and use it both during route preflight and
inside writeCustomLayers before acquiring the write transaction. Enforce all
custom-layer invariants for IDs, titles, bodies, and projections so direct
callers cannot persist invalid runtime inputs, then add direct WP1 tests
covering invalid inputs.
- Around line 608-616: Revise the Windows durability statement near the “Reads
never write” section to remove the unsupported claim that FlushFileBuffers,
MoveFileEx, and Windows CI coverage exist. Align the plan with the acceptance
gate by either specifying a Bun-supported implementation with a corresponding
Windows test, or stating that unsupported durability must fail closed with
recovery_required; do not claim APIs absent from the TypeScript source and
tests.
In `@devlog/_plan/260802_codex_set_prompt_composer/020_wp2_management_route.md`:
- Around line 84-107: The shared WP1 validation pipeline must be the sole source
of truth: in
devlog/_plan/260802_codex_set_prompt_composer/020_wp2_management_route.md lines
84-107, specify invoking the WP1 validator for request preflight and again
immediately before commit; in
devlog/_plan/260802_codex_set_prompt_composer/005_ux_design.md lines 125-135,
replace “printable Unicode” with WP1’s exact scalar-value and UTF-8-byte
validation rules so UI and route behavior remain identical.
- Around line 21-29: Drive the documented UI and API examples from
LAYER_INVENTORY to preserve the canonical taxonomy. In
devlog/_plan/260802_codex_set_prompt_composer/020_wp2_management_route.md:21-29,
classify plugins as runtime-conditional with no key; in
devlog/_plan/260802_codex_set_prompt_composer/005_ux_design.md:30-45, mark the
mockup partial or include every enumerable descriptor and the extension notice;
and in
devlog/_plan/260802_codex_set_prompt_composer/020_wp2_management_route.md:48-50,
distinguish classes lacking an upstream off-switch from those without a switch
in this panel.
In `@devlog/_plan/260802_codex_set_prompt_composer/040_wp4_layer_rows.md`:
- Around line 28-42: Remove extension-unknown from the inventory row model and
any table-driven row tests, since LAYER_INVENTORY and inventory[].class only
describe actual entries. Add separate response metadata such as
extensionLayersEnumerable: false, and update rendering to show an
unavailable-status message rather than an extension count.
In
`@devlog/_plan/260802_codex_set_prompt_composer/070_wp7_docs_and_verification.md`:
- Line 75: Add a focused shell test for the Logs-style left/right two-panel
layout, asserting the panel regions, layout classes, and tab-to-panel
relationship rather than only lazy mounting or history behavior. Update the
acceptance-table row for “Logs-style left/right panels” to reference the new
test instead of WP3 tests 4-6.
In `@devlog/_plan/260802_codex_set_prompt_composer/090_theme_deferred.md`:
- Around line 28-30: Add a prerequisite work item before Theme reuse that
implements and tests the shared writer in prompt-layers.ts, including
EOL-preserving scoped edits and atomic writes; update the Theme plan to depend
on this work or explicitly establish its own write mechanism rather than
assuming nonexistent functionality.
In `@devlog/_plan/260802_wt1_update_path_star_prompt/000_plan.md`:
- Line 8: Resolve the priority contradiction in the plan’s Bug C scope: either
mark Bug C as required and add explicit acceptance criteria covering the
fail-closed cache check and persisted-field sanitization, or remove Bug C from
the “all three bugs must-fix” statement. Ensure the final wording does not allow
completion without those safeguards.
- Around line 13-16: Update src/update/notify.ts, specifically isNewer and its
version-cache comparison flow, so a stable latest target with the same
major.minor.patch core as an installed preview is treated as newer, while
preview targets remain rejected for the latest channel. In
devlog/_plan/260802_wt1_update_path_star_prompt/000_plan.md lines 13-16,
document this same-core preview-to-stable behavior; in 010_implementation.md
lines 13-17, add the activation scenario 2.8.2-preview.* → 2.8.2 expecting
isNewer() to return true.
In `@devlog/_plan/260802_wt1_update_path_star_prompt/010_implementation.md`:
- Line 23: Choose and document a single cooldown policy before implementing the
deferral record; use a seven-day cooldown with an ISO timestamp in
.star-deferred. Define how the current time is obtained and parsed, how missing
or malformed records behave, and ensure upgrades do not reset or bypass the
cooldown. Update the regression tests and printAgentDeferral flow in
src/cli/star-prompt.ts to enforce one relay per deferral event.
In `@devlog/_plan/260802_wt2_zero_leak_bounds/000_plan.md`:
- Around line 20-25: Update the “Key review questions for the executing session”
section to define the shared retained-state accounting contract: require every
affected store to register via registerRetainedStore() and expose bytes,
evictableBytes, pinnedBytes, and oldestAt. Require evictOldest() to report
released bytes, with enforcement verifying the corresponding decrease in bytes,
and add contract tests covering pinned-only, zero-release, and stale-accounting
cases.
In `@devlog/_plan/260802_wt2_zero_leak_bounds/010_implementation.md`:
- Around line 13-18: Replace the generic per-PR acceptance pattern with six
distinct acceptance rows for `#847`, `#844`, `#845`, `#841`, `#843`, and `#840`. For each
row, specify its cap, typed failure condition, forbidden success side effect,
and RSS-style retention invariant, including the exact contract: no completed
tool item/clean DONE, incomplete-frame failure, protobuf setBlobArgs error, no
acknowledged store, retained prior mappings, or memo release as applicable.
- Around line 9-11: Update the ordered list in the plan so the two Cursor
entries are separate items numbered 3 and 4, preserving their existing text and
relationship; renumber the following Antigravity replay retention and Windows
ACL memo release entries to 5 and 6.
In `@devlog/_plan/260802_wt3_provider_wire/000_plan.md`:
- Line 3: Remove the user-specific absolute paths and local worktree layout from
the plan’s “Executing worktree” description, replacing them with
repository-relative worktree names or neutral placeholders while preserving the
intended branch and worktree context.
In `@devlog/_plan/260802_wt4_server_config_security/000_plan.md`:
- Line 20: Update the destination-resolution logic described in “Fix shape” to
handle both existing and new paths: use realpath on an existing destination, but
for a missing destination or dangling symlink resolve the parent directory and
append the original basename. Preserve the original destination path for
ownership bookkeeping while selecting the temporary-file location beside the
resolved target.
In `@devlog/_plan/260802_wt4_server_config_security/010_implementation.md`:
- Line 24: Update atomicWriteFile and atomicWriteFileAsync to resolve only the
existing parent path when the destination is new or dangling, while resolving
the destination itself when it exists, so temporary files are placed beside the
real target and symlinks remain intact. Cover existing symlinks, existing
regular files, new destinations, and dangling symlinks with tests, and keep the
original destination value for recordOwnedConfigPath and all cleanup
bookkeeping.
In `@docs-site/src/content/docs/guides/providers.md`:
- Around line 317-325: Update the Copilot paragraph to identify GitHub Copilot
as an experimental OAuth provider and state that its provider-wide default
adapter is openai-chat. Explicitly describe the seven listed models as registry
defaults, while preserving the existing adapter precedence and model-specific
Responses override guidance.
In `@src/codex/prompt-layers.ts`:
- Around line 228-243: The revision calculation currently hashes decoded strings
with ambiguous sentinels and delimiters, allowing distinct file contents to
collide. Update readFileOrNull to return raw Buffer values and revise
computeRevision to hash each file using an explicit presence marker, length
prefix, and raw bytes, preserving distinct absent, sentinel-like,
delimiter-like, and invalid-byte content. Update the associated codex
prompt-layer tests to cover invalid bytes and presence/framing boundaries.
In `@src/providers/registry.ts`:
- Line 227: Extend the generated-profile coverage in the claude-agents injection
test to include anthropic/claude-opus-4-8 and assert the generated marker
claude-opus-4-8[1m], while preserving the existing registry context-window
assertion.
In `@tests/deepseek-reasoning-replay.test.ts`:
- Around line 69-84: Add a focused test beside the existing DeepSeek and OpenAI
tests in the flat tests suite that creates a DeepSeek provider with
preserveResponsesReasoningContent explicitly set to false, runs
enrichProviderFromRegistry and buildBody, and asserts the resulting reasoning
content is blank. Preserve the existing registry-backfill and OpenAI coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 675752f4-c82e-4fbd-9a1f-4e3279af8642
⛔ Files ignored due to path filters (1)
src/generated/jawcode-model-metadata.tsis excluded by!**/generated/**
📒 Files selected for processing (59)
devlog/_plan/260802_client_toggle_api/000_plan.mddevlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.mddevlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.mddevlog/_plan/260802_client_toggle_api/003_api_design_options.mddevlog/_plan/260802_client_toggle_api/004_ux_design.mddevlog/_plan/260802_codex_set_prompt_composer/000_plan.mddevlog/_plan/260802_codex_set_prompt_composer/001_prompt_layer_inventory.mddevlog/_plan/260802_codex_set_prompt_composer/002_injection_paths_and_presets.mddevlog/_plan/260802_codex_set_prompt_composer/003_config_write_semantics.mddevlog/_plan/260802_codex_set_prompt_composer/004_surface_inventory.mddevlog/_plan/260802_codex_set_prompt_composer/005_ux_design.mddevlog/_plan/260802_codex_set_prompt_composer/010_wp1_prompt_layers_core.mddevlog/_plan/260802_codex_set_prompt_composer/020_wp2_management_route.mddevlog/_plan/260802_codex_set_prompt_composer/030_wp3_page_shell.mddevlog/_plan/260802_codex_set_prompt_composer/040_wp4_layer_rows.mddevlog/_plan/260802_codex_set_prompt_composer/050_wp5_custom_layers.mddevlog/_plan/260802_codex_set_prompt_composer/060_wp6_presets_and_linter.mddevlog/_plan/260802_codex_set_prompt_composer/070_wp7_docs_and_verification.mddevlog/_plan/260802_codex_set_prompt_composer/090_theme_deferred.mddevlog/_plan/260802_wt1_update_path_star_prompt/000_plan.mddevlog/_plan/260802_wt1_update_path_star_prompt/010_implementation.mddevlog/_plan/260802_wt2_zero_leak_bounds/000_plan.mddevlog/_plan/260802_wt2_zero_leak_bounds/010_implementation.mddevlog/_plan/260802_wt3_provider_wire/000_plan.mddevlog/_plan/260802_wt3_provider_wire/010_bug_a_copilot_mixed_wire.mddevlog/_plan/260802_wt3_provider_wire/020_bug_b_deepseek_service_tier.mddevlog/_plan/260802_wt3_provider_wire/030_bug_c_claude_1m_windows.mddevlog/_plan/260802_wt4_server_config_security/000_plan.mddevlog/_plan/260802_wt4_server_config_security/010_implementation.mddevlog/_plan/260802_wt5_windows_service_doctor/000_plan.mddevlog/_plan/260802_wt5_windows_service_doctor/010_implementation.mddocs-site/src/content/docs/guides/codex-app-models.mddocs-site/src/content/docs/guides/providers.mddocs-site/src/content/docs/ja/guides/codex-app-models.mddocs-site/src/content/docs/ja/reference/configuration/providers.mddocs-site/src/content/docs/ko/guides/codex-app-models.mddocs-site/src/content/docs/ko/reference/configuration/providers.mddocs-site/src/content/docs/reference/configuration/providers.mddocs-site/src/content/docs/ru/guides/codex-app-models.mddocs-site/src/content/docs/ru/reference/configuration/providers.mddocs-site/src/content/docs/zh-cn/guides/codex-app-models.mddocs-site/src/content/docs/zh-cn/reference/configuration/providers.mdscripts/generate-jawcode-metadata.tssrc/adapters/openai-responses.tssrc/claude/agents-inject.tssrc/codex/prompt-layers.tssrc/config.tssrc/providers/derive.tssrc/providers/registry.tssrc/router.tssrc/server/responses/core.tssrc/types.tstests/claude-agents-inject.test.tstests/codex-catalog.test.tstests/codex-prompt-layers.test.tstests/deepseek-reasoning-replay.test.tstests/github-copilot-wire-defaults.test.tstests/provider-registry-parity.test.tstests/service-tier-capability.test.ts
| ### Q1 — cc-switch reference mechanics (doc 001) | ||
|
|
||
| - How does cc-switch apply a provider into each app's config (writer modules, | ||
| atomic write, backup/restore), and how does it remove or restore one? | ||
| - Does it offer any local HTTP API / relay ("universal endpoint") as an | ||
| alternative to file writes? | ||
| - What state does it own (`~/.cc-switch/config.json`) versus mutate in the | ||
| client's files? |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use the cc-switch state paths from the research result.
Line 47 names ~/.cc-switch/config.json as cc-switch-owned state. 001_ccswitch_toggle_analysis.md identifies ~/.cc-switch/cc-switch.db as the provider store and separates settings.json and backups/. Update Q1 to distinguish provider state, device settings, and backups. Otherwise, this plan can drive the wrong ownership model.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_client_toggle_api/000_plan.md` around lines 41 - 48,
Update Q1 in the cc-switch reference mechanics section to distinguish
cc-switch’s provider store at ~/.cc-switch/cc-switch.db, device settings in
settings.json, and backup data under backups/. Replace the current ownership
reference to ~/.cc-switch/config.json and clarify which state cc-switch owns
versus which client files it mutates.
| Research only. No diffs here. Sources were opened against upstream `main` on | ||
| 2026-08-02 via the GitHub API (research lane Zeno, main-agent spot-check of the | ||
| two load-bearing claims). | ||
|
|
||
| cc-switch v3.17.0 (Tauri 2, Rust + React) manages Claude Code, Claude Desktop, | ||
| Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, and Hermes Agent. It is the | ||
| closest shipping implementation of the "on/off switch per client app" this unit | ||
| evaluates. Its own provider store is `~/.cc-switch/cc-switch.db` (SQLite), with | ||
| `settings.json` for device preferences and `backups/` for database backups | ||
| ([README.md#L302-L305](https://github.com/farion1231/cc-switch/blob/main/README.md#L302-L305), | ||
| [database/mod.rs#L96-L109](https://github.com/farion1231/cc-switch/blob/main/src-tauri/src/database/mod.rs#L96-L109)). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
printf '%s\n' '--- relevant files ---'
git ls-files 'devlog/_plan/260802_client_toggle_api/*'
printf '%s\n' '--- cited sections ---'
for f in \
devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md \
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md \
devlog/_plan/260802_client_toggle_api/003_api_design_options.md \
devlog/_plan/260802_client_toggle_api/004_ux_design.md
do
echo "--- $f ---"
wc -l "$f"
done
sed -n '1,25p' devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md
sed -n '15,65p' devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
sed -n '95,125p' devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
sed -n '165,180p' devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
sed -n '155,220p' devlog/_plan/260802_client_toggle_api/003_api_design_options.md
sed -n '1,25p' devlog/_plan/260802_client_toggle_api/004_ux_design.mdRepository: lidge-jun/opencodex
Length of output: 15750
🌐 Web query:
Resolve the immutable revisions and documentation URLs for farion1231/cc-switch v3.17.0, NousResearch/hermes-agent main documentation revision around 2026-08-02, and openclaw/openclaw main plus docs.openclaw.ai revision around 2026-08-02. Include whether each source uses a moving branch, a release tag, or an exact commit.
💡 Result:
The requested revisions and documentation sources as of August 2, 2026, are resolved as follows: farion1231/cc-switch v3.17.0 - Documentation URL: The documentation for this version is located at https://github.com/farion1231/cc-switch/blob/v3.17.0/docs/release-notes/v3.17.0-en.md [1]. - Revision Type: This is an immutable release tag (v3.17.0) [2][1]. NousResearch/hermes-agent (main) - Documentation URL: The documentation is hosted at https://hermes-agent.nousresearch.com/docs/ [3][4]. - Revision Type: This uses a moving branch (main) [5][6]. As of August 2, 2026, the documentation at this URL reflects the latest state of the main branch [3]. openclaw/openclaw (main) and docs.openclaw.ai - Documentation URL: The documentation is hosted at https://docs.openclaw.ai [7][8]. The source of truth for these documents resides in the main branch of the https://github.com/openclaw/openclaw repository [9][10]. - Revision Type: Both the main repository and the documentation site use a moving branch (main) [9][8][10]. The docs.openclaw.ai site is a mirror that syncs from the main branch of the source repository [10].
Citations:
- 1: https://github.com/farion1231/cc-switch/blob/v3.17.0/docs/release-notes/v3.17.0-en.md
- 2: https://github.com/farion1231/cc-switch/releases/tag/v3.17.0
- 3: https://hermes-agent.nousresearch.com/docs/
- 4: https://github.com/nousresearch/hermes-agent
- 5: https://github.com/NousResearch/hermes-agent/blob/main/website/docs/getting-started/installation.md
- 6: https://github.com/NousResearch/hermes-agent/blob/main/website/docs/reference/tools-reference.md
- 7: https://docs.openclaw.ai/
- 8: https://github.com/openclaw/openclaw
- 9: https://github.com/openclaw/openclaw/blob/main/docs/index.md
- 10: https://github.com/openclaw/docs
🏁 Script executed:
printf '%s\n' '--- exact cited ranges ---'
nl -ba devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md | sed -n '1,18p'
nl -ba devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md | sed -n '15,65p'
nl -ba devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md | sed -n '100,125p'
nl -ba devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md | sed -n '168,178p'
nl -ba devlog/_plan/260802_client_toggle_api/003_api_design_options.md | sed -n '1,22p;160,175p;210,218p'
printf '%s\n' '--- repository and source URLs ---'
git remote -v
rg -n 'github\.com|docs\.openclaw|hermes-agent|cc-switch|Upstream:|Sources were opened' \
devlog/_plan/260802_client_toggle_api
printf '%s\n' '--- GitHub API tag/ref metadata ---'
for spec in \
'farion1231/cc-switch refs/tags/v3.17.0' \
'NousResearch/hermes-agent refs/heads/main' \
'openclaw/openclaw refs/heads/main'
do
set -- $spec
url="https://api.github.com/repos/$1/git/$2"
echo "--- $url ---"
curl -LfsS "$url" | jq '{ref:.ref, object:.object}'
doneRepository: lidge-jun/opencodex
Length of output: 10046
🏁 Script executed:
printf '%s\n' '--- exact cited ranges ---'
for spec in \
'devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md:1:18' \
'devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md:15:65' \
'devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md:100:125' \
'devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md:168:178' \
'devlog/_plan/260802_client_toggle_api/003_api_design_options.md:1:22' \
'devlog/_plan/260802_client_toggle_api/003_api_design_options.md:160:175' \
'devlog/_plan/260802_client_toggle_api/003_api_design_options.md:210:218'
do
file=${spec%%:*}
rest=${spec#*:}
start=${rest%%:*}
end=${rest##*:}
echo "--- $file:$start-$end ---"
awk -v s="$start" -v e="$end" 'NR >= s && NR <= e { printf "%6d %s\n", NR, $0 }' "$file"
done
printf '%s\n' '--- immutable ref discovery ---'
for repo in farion1231/cc-switch NousResearch/hermes-agent openclaw/openclaw; do
echo "--- $repo ---"
git ls-remote --refs --tags "https://github.com/$repo.git" 'refs/tags/v3.17.0' 'refs/tags/*' 2>/dev/null |
grep -E 'v3\.17\.0$|v3\.17\.0\^\{\}$' | tail -5 || true
git ls-remote "https://github.com/$repo.git" \
'refs/heads/main' 'refs/tags/v3.17.0^{}' 2>/dev/null || true
doneRepository: lidge-jun/opencodex
Length of output: 12060
🌐 Web query:
site:github.com/NousResearch/hermes-agent/commits/main "2026-08-02" OR site:github.com/openclaw/openclaw/commits/main "2026-08-02"
💡 Result:
No matching GitHub commit pages were found for either repository on August 2, 2026:
NousResearch/hermes-agentopenclaw/openclaw
The search results did not surface commits dated 2026-08-02 for either repository.
Pin research sources to immutable revisions.
- In
001_ccswitch_toggle_analysis.md:3-13, replace everyblob/maincc-switch link withblob/v3.17.0. - In
002_client_toggle_matrix.md:25-27and:56-58, record the exact Hermes Agent and OpenClaw commits used on 2026-08-02 and link each citation to its commit.
Moving main links make these load-bearing claims non-reproducible. Do not use the current branch heads as substitutes for the revisions read on 2026-08-02.
📍 Affects 2 files
devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md#L3-L13(this comment)devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L25-L27devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L56-L58
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md` around
lines 3 - 13, Pin all cited research sources to immutable revisions: in
devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md:3-13,
replace every cc-switch blob/main link with blob/v3.17.0; in
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md:25-27 and
:56-58, identify the exact Hermes Agent and OpenClaw commits used on 2026-08-02
and link each citation to its commit, without substituting current branch heads.
| All four of this unit's target clients (Hermes, OpenClaw, and by their config | ||
| shapes Kimi Code and Gajae Code) are **additive-class**: a toggle writes and | ||
| removes one provider entry, never a whole-file takeover. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis\.md|.*cc.?switch.*|.*toggle.*|.*kimi.*|.*gajae.*|.*hermes.*|.*openclaw.*)' || true
printf '%s\n' '--- target document ---'
wc -l devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md
cat -n devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md
printf '%s\n' '--- references to the four clients and classification ---'
rg -n -i -C 4 'Hermes|OpenClaw|Kimi Code|Gajae Code|additive-class|whole-file|provider entry|removal|remove' . \
-g '!node_modules' -g '!dist' -g '!build' || trueRepository: lidge-jun/opencodex
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -eu
log=/tmp/coderabbit-shell-logs/shell-output-bxl5Tv
printf '%s\n' '--- target-document output in prior log ---'
rg -n -A 8 -B 8 '001_ccswitch_toggle_analysis\.md|additive-class|Kimi Code|Gajae Code' "$log" | head -n 240
printf '%s\n' '--- focused tracked-file search ---'
git ls-files | rg 'ccswitch|cc-switch|toggle|kimi|gajae|hermes|openclaw' | head -n 240
printf '%s\n' '--- focused repository search ---'
rg -n -i -C 3 'cc.?switch|additive-class|Kimi Code|Gajae Code|OpenClaw|Hermes Agent' \
devlog src tests .github 2>/dev/null | head -n 400 || trueRepository: lidge-jun/opencodex
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- cc-switch analysis ---'
cat -n devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md
printf '%s\n' '--- client matrix ---'
cat -n devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
printf '%s\n' '--- design sections that rely on the classification ---'
sed -n '140,205p' devlog/_plan/260802_client_toggle_api/003_api_design_options.md
sed -n '205,225p' devlog/_plan/260802_client_toggle_api/004_ux_design.mdRepository: lidge-jun/opencodex
Length of output: 27432
🌐 Web query:
MoonshotAI kimi-code e22479a core-impl.ts provider remove cascades config TOML unknown fields preserved
💡 Result:
The query refers to technical components and configuration management patterns found in projects like MoonshotAI's Kimi Code CLI [1][2] and related agent-core architectures [3]. Regarding the specific elements mentioned: Provider Removal and Cascades: In the context of Kimi Code's architecture (specifically packages/agent-core-v2), the ProviderService manages provider configurations [3]. When a provider is removed, the system ensures consistency by clearing dependent configurations, such as the default provider, if it matches the one being deleted [3][4]. The removal process typically involves updating the entire provider section by replacing it with the filtered set [3][4], rather than just modifying the single entry in place, to ensure the state remains synchronized [3][4]. TOML Configuration and Preservation of Unknown Fields: The requirement to preserve unknown fields in TOML configuration files is a common challenge in CLI tool development. When tools round-trip configurations (parse TOML -> edit in-memory -> write back to TOML), naive serialization often drops comments or keys not explicitly defined in the application's configuration schema [5][6][7]. To address this, developers typically adopt one of two strategies: 1. Recursive Preservation: Using libraries like toml_edit to parse the file into a document model that maintains key order, comments, and structure, allowing the application to update specific fields while leaving the rest of the document untouched [6]. 2. Flattening/Extra Fields: Using Serde's #[serde(flatten)] attribute to capture unknown keys into a catch-all map (often named extra), which can then be serialized back into the file [8][7][9]. The specific commit reference e22479a appears to be a local or specific internal identifier not indexed in public repositories for MoonshotAI/kimi-code [1][10], but the behaviors described align with standard practices for maintaining configuration durability in agent-based CLI tools [3][8][7].
Citations:
- 1: https://github.com/MoonshotAI/kimi-code
- 2: https://github.com/MoonshotAI/kimi-code/tree/main/apps/kimi-code
- 3: https://github.com/xy200303/spec-kimi-code/blob/main/packages/agent-core-v2/src/app/provider/providerService.ts
- 4: https://github.com/xy200303/spec-kimi-code/blob/main/packages/agent-core-v2/test/app/provider/provider.test.ts
- 5: fix(config): preserve nested unknown keys on save max-sixty/worktrunk#2180
- 6: usedetail/cli@cf07687
- 7: soorya-u/dotagents@6966c99
- 8: feat: preserve unknown config keys and enforce list-replace merge semantics soorya-u/dotagents#165
- 9: https://docs.rs/toxi-config/latest/src/toxi_config/lib.rs.html
- 10: https://github.com/MoonshotAI/kimi-code/releases/tag/%40moonshot-ai%2Fkimi-code%400.29.0
🌐 Web query:
Yeachan-Heo gajae-code 5f2e7cd models.yml provider setup remove atomic writer preserves unrelated configuration
💡 Result:
The query refers to functionality within the Yeachan-Heo/gajae-code project (also known as GJC) [1][2]. While there is no specific public commit with the hash 5f2e7cd documented in the repository's public history, the project manages configuration through a models.yml file [1][3] and uses a configuration system that interacts with provider setups [4][5]. In this context, the project's configuration management has evolved to improve the robustness of how models.yml is handled: 1. Preservation of Unrelated Configuration: The project's configuration loaders (e.g., ModelsConfigFile) are designed to merge and validate configurations [5]. When updating provider settings, the system typically preserves unrelated configuration keys rather than overwriting the entire file, by applying structured schema validation (using Zod) and partial updates [5]. 2. Atomic Writes and Provider Setup: To prevent configuration corruption during updates or provider setup—specifically when using commands like gjc setup provider—the system utilizes atomic write patterns. This ensures that when a configuration file is updated, the operation either completes fully or leaves the existing configuration intact, preventing partial or malformed writes that could inadvertently strip out unrelated settings [4][5]. 3. Validation: Recent improvements have focused on ensuring that unknown or unsupported provider keys in models.yml (such as legacy requestTransform or wireModelId fields) are either handled or result in clear validation warnings rather than silent failures [1][5]. If you are encountering issues where unrelated settings are being removed, it is likely related to an older version of the configuration logic or a failure to strictly adhere to the updated ProviderConfigSchema, which defines the expected structure for provider-level overrides [5]. Using the most recent version of the CLI is recommended to ensure robust configuration management [2].
Citations:
- 1: Improve GJC proxy config discoverability and bridge-command UX from smoke test Yeachan-Heo/gajae-code#96
- 2: https://github.com/Yeachan-Heo/gajae-code
- 3: Improve custom provider UX and add MiniMax/GLM support Yeachan-Heo/gajae-code#82
- 4: Source-bundle GJC delegation prompts Yeachan-Heo/gajae-code#19
- 5: Yeachan-Heo/gajae-code@975a342
🌐 Web query:
farion1231 cc-switch provider remove_provider_from_live_config Hermes OpenClaw additive config
💡 Result:
In the context of the CC Switch application (maintained by farion1231), the function remove_provider_from_live_config is a specific API command used to remove a provider from the live configuration file for supported AI tools that utilize an additive configuration mode [1]. Additive Mode and Configuration Management: CC Switch supports an additive mode for tools such as OpenCode, OpenClaw, and Hermes Agent [1]. In this mode, multiple providers can remain active simultaneously within the same configuration file [1]. When managing these providers, the application tracks their presence in the live configuration using an isInConfig property [1]. The remove_provider_from_live_config command is specifically designed to perform this removal at the live file level without deleting the provider data from the underlying database [2][1]. Technical Context: - CC Switch is an all-in-one assistant for tools including Claude Code, Codex, OpenCode, OpenClaw, and Hermes Agent [3][4]. - The remove_provider_from_live_config API was implemented to ensure that when a user removes a provider in the UI, the live configuration file is updated correctly, while maintaining the integrity of the provider records in the app's SQLite database [3][2]. - This command is part of the broader provider management system that abstracts the configuration differences between the various native formats of the supported AI tools [1]. Note on Migration: While OpenClaw and Hermes share configuration concepts, migrating between them (using tools like openclaw-to-hermes or bundled migration providers) involves distinct processes—such as mapping custom providers and migrating API keys to a.env file—which are separate from the live config management performed by CC Switch [5][6][7][8].
Citations:
- 1: https://deepwiki.com/farion1231/cc-switch/4-provider-management
- 2: farion1231/cc-switch@ad6f5b3
- 3: https://github.com/farion1231/cc-switch
- 4: https://github.com/farion1231/cc-switch?utm=api.tokenhot_ai
- 5: https://hermes-agent.nousresearch.com/docs/guides/migrate-from-openclaw
- 6: https://docs.openclaw.ai/install/migrating-hermes
- 7: https://github.com/NousResearch/hermes-agent/blob/44ddc552/website/docs/guides/migrate-from-openclaw.md
- 8: https://github.com/0xNyk/openclaw-to-hermes
🏁 Script executed:
#!/bin/bash
set -eu
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/packages/agent-core/src/rpc/core-impl.ts \
-o "$tmp/kimi-core-impl.ts"
curl -fsSL \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/packages/agent-core/src/config/toml.ts \
-o "$tmp/kimi-toml.ts"
curl -fsSL \
https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/5f2e7cd05e8ea344991566f9ed96f1f9c66226bd/packages/coding-agent/src/cli/setup-cli.ts \
-o "$tmp/gajae-setup-cli.ts"
curl -fsSL \
https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/5f2e7cd05e8ea344991566f9ed96f1f9c66226bd/packages/coding-agent/src/config/models-config-schema.ts \
-o "$tmp/gajae-schema.ts"
curl -fsSL \
https://raw.githubusercontent.com/farion1231/cc-switch/main/src-tauri/src/services/provider/mod.rs \
-o "$tmp/ccswitch-provider.rs"
printf '%s\n' '--- Kimi provider removal ---'
rg -n -A 38 -B 12 'provider.*remove|remove.*provider|default_model|default_provider' "$tmp/kimi-core-impl.ts" | head -n 180
printf '%s\n' '--- Kimi TOML mutation/write path ---'
rg -n -A 35 -B 12 'write|serialize|smol|provider|models' "$tmp/kimi-toml.ts" | head -n 220
printf '%s\n' '--- Gajae setup mutation path ---'
rg -n -A 45 -B 15 'setup provider|models\.yml|write|atomic|rename|provider' "$tmp/gajae-setup-cli.ts" | head -n 240
printf '%s\n' '--- Gajae schema ---'
rg -n -A 35 -B 12 'ProviderConfig|providers|strict|apiKeyEnv|models' "$tmp/gajae-schema.ts" | head -n 220
printf '%s\n' '--- cc-switch additive removal implementation ---'
rg -n -A 45 -B 15 'remove_provider_from_live_config|remove.*live|live_config_managed' "$tmp/ccswitch-provider.rs" | head -n 260Repository: lidge-jun/opencodex
Length of output: 36263
🏁 Script executed:
#!/bin/bash
set -eu
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/packages/agent-core/src/rpc/core-impl.ts \
-o "$tmp/kimi-core-impl.ts"
curl -fsSL \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/packages/agent-core/src/config/toml.ts \
-o "$tmp/kimi-toml.ts"
curl -fsSL \
https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/5f2e7cd05e8ea344991566f9ed96f1f9c66226bd/packages/coding-agent/src/setup/provider-onboarding.ts \
-o "$tmp/gajae-provider-onboarding.ts"
curl -fsSL \
https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/5f2e7cd05e8ea344991566f9ed96f1f9c66226bd/packages/coding-agent/src/config/models-config.ts \
-o "$tmp/gajae-models-config.ts"
curl -fsSL \
https://raw.githubusercontent.com/farion1231/cc-switch/main/src-tauri/src/services/provider/live.rs \
-o "$tmp/ccswitch-live.rs"
printf '%s\n' '--- Kimi removal and TOML write ranges ---'
rg -n 'removeProvider|remove_provider|default_model|default_provider|atomicWrite|stringifyToml|writeConfig|writeFile' \
"$tmp/kimi-core-impl.ts" "$tmp/kimi-toml.ts"
sed -n '700,780p' "$tmp/kimi-core-impl.ts"
sed -n '450,535p' "$tmp/kimi-toml.ts"
printf '%s\n' '--- Gajae provider onboarding and write ranges ---'
rg -n 'addApiCompatibleProvider|models\.yml|writeFile|rename|atomic|remove|delete|providers' \
"$tmp/gajae-provider-onboarding.ts" "$tmp/gajae-models-config.ts"
sed -n '1,260p' "$tmp/gajae-provider-onboarding.ts"
printf '%s\n' '--- cc-switch Hermes/OpenClaw removal ranges ---'
rg -n 'remove_hermes_provider_from_live|remove_openclaw_provider_from_live|remove_provider_from_live_config|remove.*provider' \
"$tmp/ccswitch-live.rs"Repository: lidge-jun/opencodex
Length of output: 208
🏁 Script executed:
#!/bin/bash
set -u
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
fetch() {
url=$1
out=$2
if curl -fsSL "$url" -o "$out"; then
printf 'fetched %s\n' "$url"
else
printf 'failed %s\n' "$url" >&2
fi
}
fetch \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba967/packages/agent-core/src/rpc/core-impl.ts \
"$tmp/kimi-core-impl.ts"
fetch \
https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba967/packages/agent-core/src/config/toml.ts \
"$tmp/kimi-toml.ts"
fetch \
https://raw.githubusercontent.com/farion1231/cc-switch/main/src-tauri/src/services/provider/live.rs \
"$tmp/ccswitch-live.rs"
printf '%s\n' '--- Gajae repository paths at the cited commit ---'
curl -fsSL \
'https://api.github.com/repos/Yeachan-Heo/gajae-code/git/trees/5f2e7cd05e8ea344991566f9ed96f1f9c66226bd?recursive=1' |
jq -r '.tree[] | select(.path|test("provider|models-config|config.*(write|file)|setup-cli"; "i")) | .path' |
head -n 240
printf '%s\n' '--- Kimi removal and TOML write ranges ---'
rg -n 'removeProvider|remove_provider|default_model|default_provider|atomicWrite|stringifyToml|writeConfig|writeFile' \
"$tmp/kimi-core-impl.ts" "$tmp/kimi-toml.ts" || true
sed -n '700,780p' "$tmp/kimi-core-impl.ts"
sed -n '450,535p' "$tmp/kimi-toml.ts"
printf '%s\n' '--- cc-switch removal implementation ---'
rg -n -A 35 -B 12 'remove_hermes_provider_from_live|remove_openclaw_provider_from_live|remove_provider_from_live_config|remove.*provider' \
"$tmp/ccswitch-live.rs" || trueRepository: lidge-jun/opencodex
Length of output: 13708
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
analysis = Path("devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md").read_text()
matrix = Path("devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md").read_text()
claim = "All four of this unit's target clients"
assert claim in analysis
assert "Kimi provider remove" in matrix
assert "cascades" in matrix
assert "no remove command exists" in matrix
assert "file edit required" in matrix
print("001 classification claim: present")
print("Kimi: vendor removal exists and cascades dependent aliases/defaults")
print("Gajae: vendor add/replace exists; vendor removal is absent")
print("Conclusion: the four clients cannot share an unqualified one-entry removal contract")
PYRepository: lidge-jun/opencodex
Length of output: 245
Split the additive classification by client. cc-switch proves entry-scoped removal for Hermes and OpenClaw. Kimi Code removes the provider, dependent model aliases, and matching defaults before rewriting TOML; it is not a one-entry removal contract. Gajae Code provides add/replace only and has no removal command. Mark Gajae Code candidate — unverified, and require a fixture that proves disable preserves unrelated models.yml configuration before applying the additive writer contract. Update devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md:32-34.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_client_toggle_api/001_ccswitch_toggle_analysis.md` around
lines 32 - 34, The client classification at the referenced statement incorrectly
groups all four clients as additive-class. Keep Hermes and OpenClaw classified
as additive, classify Kimi Code separately because disabling removes related
provider, alias, and default entries, and mark Gajae Code as
candidate—unverified; require a fixture proving disable preserves unrelated
models.yml configuration before applying the additive writer contract.
| | Non-interactive add | No (wizard/dashboard only) | `openclaw config set/patch` | `kimi provider add <registry-url>` (registry import only) | `gjc setup provider` | | ||
| | Non-interactive remove | No | `openclaw config unset` | `kimi provider remove` (cascades) | **No** (file edit required) | | ||
| | Toggle channel of choice | file writer (or dashboard API) | **client CLI** | **registry endpoint + client CLI** | CLI add + file-writer remove | |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
for f in \
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md \
devlog/_plan/260802_client_toggle_api/003_api_design_options.md
do
echo "===== $f ====="
wc -l "$f"
case "$f" in
*002*) sed -n '1,125p;160,185p' "$f" ;;
*003*) sed -n '155,180p;205,220p' "$f" ;;
esac
done
echo "===== related references ====="
rg -n --glob '*.md' \
'Hermes|hermes setup|saveConfigRaw|kimi provider|api\.json|vendor-owned|Three of four|Option B|registry schema' \
devlog/_plan/260802_client_toggle_apiRepository: lidge-jun/opencodex
Length of output: 20861
🏁 Script executed:
#!/bin/bash
set -e
echo "===== repository files related to the client matrix ====="
git ls-files | rg '(^|/)(hermes|kimi|openclaw|gjc|client_toggle|api_design|provider)' | head -200
echo "===== source references for the documented vendor paths ====="
rg -n --hidden --glob '!devlog/_plan/260802_client_toggle_api/*.md' \
'saveConfigRaw|/api/config|/api/env|/api/model/set|provider add|provider remove|config set|config unset|setup provider|api\.json' . \
| head -250Repository: lidge-jun/opencodex
Length of output: 45663
🌐 Web query:
Search the official Hermes, Kimi Code, OpenClaw, and Gajae documentation or source repositories for the non-interactive provider add/remove and channel-toggle surfaces described in the two design documents, including Hermes dashboard provider CRUD endpoints and Kimi's kimi provider addregistryapi.json schema.
💡 Result:
The requested non-interactive provider and channel management surfaces are implemented across the documented systems as follows: Hermes Dashboard Provider CRUD Endpoints The Hermes Agent web dashboard manages providers primarily via the /api/providers/oauth and related REST endpoints [1][2]. Key CRUD-style operations include: - Provider Management: Authenticated providers (e.g., OAuth) can be retrieved via GET /api/providers/oauth and removed via DELETE /api/providers/oauth/{providerId} [1][2]. - Channel/Messaging Toggles: Messaging platforms and gateway channels are managed via /api/messaging/platforms/{id} and /api/webhooks [3]. Specific actions like enabling a channel or reporting connection status are performed through endpoints such as /api/messaging/platforms/{id}/enable [2][3]. - Memory/Provider Selection: Provider selection in the memory context is handled via PUT /api/memory/provider [3]. Kimi Code 'kimi provider add' and Registry Schema The Kimi Code CLI provides non-interactive management through the kimi provider sub-command, which mirrors TUI workflows [4][5]. - Provider Add: The kimi provider add command accepts registry URLs to import provider/model configurations [6][4]. It writes to a configuration blob that uses a CustomRegistrySource structure, containing fields such as kind (set to 'apiJson'), url, and apiKey [5]. - Registry Schema: Editable registries are typically stored in registries/{id}/api.json [7][8]. While strictly generated providers require fields like id, name, api, type, and models, the editable registry validation allows for additional, namespaced, or Kimi-specific metadata [7]. OpenClaw and Gajae Code - OpenClaw: Manages providers and models through its internal configuration file ~/.openclaw/openclaw.json and CLI commands like openclaw models set [9]. - Gajae Code: Utilizes RPC host-tool boundaries for managing integrations, recommending developers interact with coordinator bridges (e.g., Hermes/OpenClaw-style) rather than direct runtime imports to maintain policy and credential isolation [10].
Citations:
- 1: https://github.com/NousResearch/hermes-agent/blob/5e01a5db/web/src/lib/api.ts
- 2: https://github.com/NousResearch/hermes-agent/blob/af8d698b/web/src/lib/api.ts
- 3: https://hermes-agent.nousresearch.com/docs/user-guide/features/web-dashboard
- 4: https://github.com/xy200303/spec-kimi-code/blob/main/docs/en/configuration/providers.md
- 5: https://github.com/xy200303/spec-kimi-code/blob/main/apps/kimi-code/src/cli/sub/provider.ts
- 6: https://www.kimi.com/code/docs/en/kimi-code-cli/configuration/providers.html
- 7: https://github.com/ayu-exorcist/kimi-registry-adapter/blob/main/docs/configuration.md
- 8: https://github.com/ayu-exorcist/kimi-registry-adapter/blob/main/docs/state-and-update.md
- 9: https://platform.kimi.ai/docs/guide/use-kimi-in-openclaw
- 10: https://gajae-code.com/docs/hermes-mcp-bridge.html
🏁 Script executed:
#!/bin/bash
set -e
echo "===== exact affected ranges ====="
sed -n '15,23p;35,45p;103,122p;168,186p' \
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
sed -n '164,174p;188,198p;210,218p' \
devlog/_plan/260802_client_toggle_api/003_api_design_options.md
echo "===== Hermes source at the documented snapshot ====="
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/NousResearch/hermes-agent/5e01a5db/web/src/lib/api.ts \
| rg -n -C 3 'provider|/api/config|/api/env|/api/model/set|saveConfigRaw'
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/NousResearch/hermes-agent/5e01a5db/hermes_cli/web_server.py \
| rg -n -C 3 'provider|/api/config|/api/env|/api/model/set|saveConfigRaw' || true
echo "===== Kimi source at the documented snapshot ====="
for path in \
packages/agent-core/src/rpc/core-impl.ts \
packages/agent-core/src/config/toml.ts \
packages/agent-core/src/config/provider.ts \
packages/agent-core/src/cli/sub/provider.ts
do
echo "--- $path ---"
curl -L --fail --silent --show-error \
"https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/$path" \
| rg -n -C 3 'apiJson|api\.json|registry|provider add|provider remove|CustomRegistrySource|removeProvider' || true
doneRepository: lidge-jun/opencodex
Length of output: 50375
🌐 Web query:
Official source only: inspect the Hermes Agent repository at commit 5e01a5db and the MoonshotAI/kimi-code repository at commit e22479a62eed9c3b78a67b313f4332c2c0ba9670. Determine whether Hermes exposes generic provider add/remove CRUD endpoints, and identify the exact Kimi api.jsonregistry document schema required bykimi provider add .
💡 Result:
Based on an inspection of the specified repositories, the findings are as follows: Hermes Agent (Commit 5e01a5db): The Hermes Agent repository does not expose generic provider add/remove CRUD endpoints. The state management in Hermes, specifically within hermes_state.py, utilizes a SQLite-backed store that handles session and model configuration, but it lacks a generic CRUD interface for provider management [1]. Existing modifications to billing or provider fields (such as billing_provider or billing_base_url) are handled through specific, targeted state updates rather than generic CRUD endpoints [2]. Kimi Code (Commit e22479a62eed9c3b78a67b313f4332c2c0ba9670): The kimi provider add <registry-url> command expects an api.json document that adheres to a schema defining providers and models [3][4]. While the CLI documentation indicates the registry must provide the necessary metadata, the schema explicitly requires that generated providers include at least the following fields: id, name, api, type, and models [5]. Generated models within that registry document are required to have an id and name [5]. The api.json document is fetched and parsed to automatically configure providers and models entries within the Kimi Code configuration [3][6][4].
Citations:
- 1: https://github.com/NousResearch/hermes-agent/blob/5e01a5db/hermes_state.py
- 2: [Bug]:
billing_provider/billing_base_urlare write-once via COALESCE — mid-session/modelswitch leaves stale provider in session DB and misleads dashboard model cards NousResearch/hermes-agent#48248 - 3: https://www.kimi.com/code/docs/en/kimi-code-cli/configuration/providers.html
- 4: https://github.com/xy200303/spec-kimi-code/blob/main/docs/en/configuration/providers.md
- 5: https://github.com/ayu-exorcist/kimi-registry-adapter/blob/main/docs/configuration.md
- 6: https://github.com/xy200303/spec-kimi-code/blob/main/apps/kimi-code/src/cli/sub/provider.ts
🏁 Script executed:
#!/bin/bash
set -e
echo "===== Hermes route declarations ====="
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/NousResearch/hermes-agent/5e01a5db/hermes_cli/web_server.py \
| rg -n '^[[:space:]]*`@app`\.(get|put|post|delete)\("/api/(config|config/raw|providers|model)' \
| head -100
echo "===== Kimi official provider documentation ====="
curl -L --fail --silent --show-error \
https://www.kimi.com/code/docs/en/kimi-code-cli/configuration/providers.html \
| sed 's/<[^>]*>/ /g' \
| tr -s ' ' \
| rg -o -i '.{0,180}(api\.json|registry|provider add|provider remove|models|schema).{0,300}' \
| head -80
echo "===== Kimi repository paths and focused source matches ====="
for path in \
packages/agent-core/src/rpc/core-impl.ts \
packages/agent-core/src/config/toml.ts
do
tmp=$(mktemp)
curl -L --fail --silent --show-error \
"https://raw.githubusercontent.com/MoonshotAI/kimi-code/e22479a62eed9c3b78a67b313f4332c2c0ba9670/$path" > "$tmp"
echo "--- $path ---"
rg -n -C 5 'apiJson|api\.json|registry|provider.*remove|remove.*provider|CustomRegistrySource' \
"$tmp" | head -160
rm -f "$tmp"
doneRepository: lidge-jun/opencodex
Length of output: 10602
Make vendor integrations conditional in the design baseline.
- Hermes has authenticated
/api/config,/api/config/raw, and/api/model/setwrites, but no generic provider CRUD endpoint. Keep the dashboard as a raw-config option, not a provider-owned CRUD path, in002_client_toggle_matrix.md:19-21and:38-43. - Kimi documents registry import and removal, but the proposed opencodex
api.jsonendpoint and schema are still open. Make the Kimi path conditional in002_client_toggle_matrix.md:107-119,:172-176, and003_api_design_options.md:166-171,:192. - Link
003_api_design_options.md:214-215from each dependent section.
Otherwise, the implementation can depend on an undocumented registry contract or provider endpoint and assume cleanup behavior that the client does not provide.
📍 Affects 2 files
devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L19-L21(this comment)devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L38-L43devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L107-L119devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md#L172-L176devlog/_plan/260802_client_toggle_api/003_api_design_options.md#L166-L171devlog/_plan/260802_client_toggle_api/003_api_design_options.md#L214-L215
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md` around
lines 19 - 21, Make vendor integration paths conditional in the client-toggle
design. In devlog/_plan/260802_client_toggle_api/002_client_toggle_matrix.md
lines 19-21 and 38-43, describe Hermes dashboard access as raw-config only
rather than provider CRUD; in lines 107-119 and 172-176, mark the Kimi registry
import/removal path conditional on the undocumented opencodex api.json contract.
In devlog/_plan/260802_client_toggle_api/003_api_design_options.md lines 166-171
and 192, apply the same conditional Kimi treatment, and link lines 214-215 from
every dependent section.
| - `GET /api/client-config?client=opencode|pi` | ||
| ([model-routes.ts:236](/Users/jun/Developer/new/700_projects/opencodex/src/server/management/model-routes.ts:236)) | ||
| builds the client document from the same core the CLI uses and **never | ||
| writes** — "targeting it is the caller's explicit act" | ||
| ([config-export.ts:19](/Users/jun/Developer/new/700_projects/opencodex/src/clients/config-export.ts:19)). | ||
| - Mutating precedents on the same router (`PUT /api/disabled-models`, | ||
| `PUT /api/model-visibility`) persist only opencodex's *own* config, never | ||
| third-party files. | ||
| - The GUI renders per-client rows from its own hand-synced list | ||
| ([client-config-clients.ts:5](/Users/jun/Developer/new/700_projects/opencodex/gui/src/components/apikeys-workspace/client-config-clients.ts:5)) — | ||
| copy/download only, no apply button. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace machine-local documentation links.
Both documents cite /Users/jun/Developer/... paths. These links are not portable and prevent independent review.
devlog/_plan/260802_client_toggle_api/003_api_design_options.md#L9-L19: use repository-relative or immutable repository links.devlog/_plan/260802_client_toggle_api/004_ux_design.md#L4-L12: use repository-relative or immutable repository links.
📍 Affects 2 files
devlog/_plan/260802_client_toggle_api/003_api_design_options.md#L9-L19(this comment)devlog/_plan/260802_client_toggle_api/004_ux_design.md#L4-L12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_client_toggle_api/003_api_design_options.md` around lines
9 - 19, Replace the machine-local /Users/jun/Developer/... links in
devlog/_plan/260802_client_toggle_api/003_api_design_options.md lines 9-19 and
devlog/_plan/260802_client_toggle_api/004_ux_design.md lines 4-12 with
repository-relative links or immutable repository URLs, preserving the
referenced symbols and destinations.
|
|
||
| File map: | ||
|
|
||
| - MODIFY `src/config.ts:107` (`atomicWriteFile`) and `:184` (`atomicWriteFileAsync`) — resolve the destination through realpath before choosing the temp-file location, so the rename lands beside the real file and the symlink survives. Verified basis: POSIX.1-2024 `rename()` + Linux `rename(2)`: "if newpath refers to a symbolic link, the link will be overwritten." |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not require the full destination to exist.
Line [24] should distinguish existing symlinks, existing regular files, new files, and dangling symlinks. A direct realpath(destination) call fails for new and dangling destinations. Add tests for each case and preserve the original destination for recordOwnedConfigPath and related cleanup bookkeeping.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260802_wt4_server_config_security/010_implementation.md` at line
24, Update atomicWriteFile and atomicWriteFileAsync to resolve only the existing
parent path when the destination is new or dangling, while resolving the
destination itself when it exists, so temporary files are placed beside the real
target and symlinks remain intact. Cover existing symlinks, existing regular
files, new destinations, and dangling symlinks with tests, and keep the original
destination value for recordOwnedConfigPath and all cleanup bookkeeping.
| Copilot fronts a mixed-wire catalog: its GPT-5 family (`gpt-5.3-codex`, `gpt-5.4`, | ||
| `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`) rejects | ||
| `/chat/completions` for agent traffic, so opencodex routes those models over the | ||
| Responses API by built-in default while every other Copilot model stays on chat | ||
| completions. The precedence is: hard wire pin → your explicit | ||
| [`modelAdapters`](/reference/configuration/providers/) entry → registry default → | ||
| provider-wide adapter. To opt a model without a built-in default (for example | ||
| `gpt-5.4-nano`) into Responses, set `"modelAdapters": { "gpt-5.4-nano": "openai-responses" }`. | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Document Copilot's experimental status and provider-wide adapter.
The changed paragraph lists the mixed-wire models and precedence correctly, but it omits two required facts:
- GitHub Copilot is an experimental OAuth provider.
- Its provider-wide default adapter is
openai-chat.
Also call the seven-model selection a registry default. This distinguishes the baseline adapter from model-specific Responses overrides.
Proposed documentation fix
-Copilot fronts a mixed-wire catalog: its GPT-5 family ...
+GitHub Copilot is an experimental OAuth provider with an `openai-chat`
+provider-wide default. It fronts a mixed-wire catalog: its GPT-5 family ...
-Responses API by built-in default ...
+Responses API by registry default ...As per path instructions, the provider guide must identify GitHub Copilot as experimental OAuth and document its openai-chat default.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs-site/src/content/docs/guides/providers.md` around lines 317 - 325,
Update the Copilot paragraph to identify GitHub Copilot as an experimental OAuth
provider and state that its provider-wide default adapter is openai-chat.
Explicitly describe the seven listed models as registry defaults, while
preserving the existing adapter precedence and model-specific Responses override
guidance.
Source: Path instructions
| function readFileOrNull(path: string): string | null { | ||
| try { | ||
| if (!existsSync(path)) return null; | ||
| return readFileSync(path, "utf8"); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export function computeRevision(configBytes: string | null, storeBytes: string | null): string { | ||
| const hash = createHash("sha256"); | ||
| hash.update("cfg:"); | ||
| hash.update(configBytes ?? "\0absent"); | ||
| hash.update("\nstore:"); | ||
| hash.update(storeBytes ?? "\0absent"); | ||
| return `sha256:${hash.digest("hex")}`; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Find every caller that must migrate from string revisions to Buffer revisions.
rg -n -C 3 --glob '*.ts' '\b(computeRevision|readFileBytes)\s*\(' .Repository: lidge-jun/opencodex
Length of output: 2133
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- prompt-layers structure and revision code ---'
ast-grep outline src/codex/prompt-layers.ts
sed -n '1,280p' src/codex/prompt-layers.ts
printf '%s\n' '--- revision references and file-read helpers ---'
rg -n -C 5 --glob '*.ts' '\b(computeRevision|readFileOrNull|revision)\b' src testsRepository: lidge-jun/opencodex
Length of output: 31968
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- all repository references ---'
rg -n -C 3 '\b(computeRevision|readFileBytes|readFileOrNull)\b' . || true
printf '%s\n' '--- imports of prompt-layers ---'
rg -n -C 3 'prompt-layers|computeRevision|readFileBytes' src tests --glob '*.{ts,tsx,js,jsx}'
printf '%s\n' '--- standalone framing and UTF-8 collision probe ---'
node - <<'JS'
const { createHash } = require("node:crypto");
function revision(config, store) {
const hash = createHash("sha256");
hash.update("cfg:");
hash.update(config ?? "\0absent");
hash.update("\nstore:");
hash.update(store ?? "\0absent");
return `sha256:${hash.digest("hex")}`;
}
const invalidA = Buffer.from([0xc3, 0x28]);
const invalidB = Buffer.from([0xe2, 0x28, 0xa1]);
const decodedA = invalidA.toString("utf8");
const decodedB = invalidB.toString("utf8");
console.log(JSON.stringify({
invalidA: [...invalidA],
invalidB: [...invalidB],
decodedA: [...decodedA].map(ch => ch.codePointAt(0)),
decodedB: [...decodedB].map(ch => ch.codePointAt(0)),
decodedEqual: decodedA === decodedB,
revisionEqualAfterDecode: revision(decodedA, null) === revision(decodedB, null),
absentVsPresentMarker: revision(null, null) === revision("\0absent", null),
}));
JSRepository: lidge-jun/opencodex
Length of output: 7380
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
node - <<'JS'
const { createHash } = require("node:crypto");
function revision(config, store) {
const hash = createHash("sha256");
hash.update("cfg:");
hash.update(config ?? "\0absent");
hash.update("\nstore:");
hash.update(store ?? "\0absent");
return hash.digest("hex");
}
const invalidA = Buffer.from([0xc0]);
const invalidB = Buffer.from([0xc1]);
const decodedA = invalidA.toString("utf8");
const decodedB = invalidB.toString("utf8");
const delimiter = "\nstore:";
const configWithDelimiter = `prefix${delimiter}`;
const storeOne = "value";
const configWithoutDelimiter = "prefix";
const storeWithDelimiter = `${delimiter}${storeOne}`;
console.log(JSON.stringify({
invalidA: [...invalidA],
invalidB: [...invalidB],
decodedA: [...decodedA].map(ch => ch.codePointAt(0)),
decodedB: [...decodedB].map(ch => ch.codePointAt(0)),
decodedEqual: decodedA === decodedB,
revisionEqualAfterDecode: revision(decodedA, null) === revision(decodedB, null),
delimiterPairOne: [configWithDelimiter, storeOne],
delimiterPairTwo: [configWithoutDelimiter, storeWithDelimiter],
delimiterRevisionEqual: revision(configWithDelimiter, storeOne) === revision(configWithoutDelimiter, storeWithDelimiter),
}));
JSRepository: lidge-jun/opencodex
Length of output: 405
Hash raw file bytes with unambiguous framing.
src/codex/prompt-layers.ts:228-243 decodes files as UTF-8 before hashing. Distinct invalid bytes such as [0xc0] and [0xc1] both decode to U+FFFD, so they produce the same revision. The "\0absent" sentinel and "\nstore:" delimiter can also collide with present file content.
Read files as Buffer values without an encoding. Hash an explicit presence byte, length prefix, and raw bytes for each file. Update tests/codex-prompt-layers.test.ts:155-172 for invalid-byte, sentinel, delimiter, and presence-framing cases.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/codex/prompt-layers.ts` around lines 228 - 243, The revision calculation
currently hashes decoded strings with ambiguous sentinels and delimiters,
allowing distinct file contents to collide. Update readFileOrNull to return raw
Buffer values and revise computeRevision to hash each file using an explicit
presence marker, length prefix, and raw bytes, preserving distinct absent,
sentinel-like, delimiter-like, and invalid-byte content. Update the associated
codex prompt-layer tests to cover invalid bytes and presence/framing boundaries.
| // devlog/_plan/260710_provider_hardening/001_research_frontier.md. | ||
| const ANTHROPIC_MODELS = ["claude-fable-5", "claude-sonnet-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"]; | ||
| const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-haiku-4-5": 200_000 }; | ||
| const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-opus-4-7": 1_000_000, "claude-opus-4-6": 1_000_000, "claude-sonnet-4-6": 1_000_000, "claude-haiku-4-5": 200_000 }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether claude-opus-4-8 is new in this diff and whether it has test coverage elsewhere.
set -euo pipefail
echo "--- git history for the ANTHROPIC_MODEL_CONTEXT_WINDOWS line ---"
git log -p -1 -- src/providers/registry.ts | grep -n -B2 -A2 "ANTHROPIC_MODEL_CONTEXT_WINDOWS" || true
echo "--- references to claude-opus-4-8 across the repo ---"
rg -n "claude-opus-4-8" --glob '!node_modules'Repository: lidge-jun/opencodex
Length of output: 276
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- repository state and changed files ---"
git status --short
git diff --stat
git diff -- src/providers/registry.ts tests/claude-agents-inject.test.ts
echo "--- registry context-window declaration and consumers ---"
rg -n -A8 -B4 "ANTHROPIC_MODEL_CONTEXT_WINDOWS|buildClaudeContextWindows|withSubagentContextMarker" src/providers/registry.ts src tests
echo "--- all opus-4-8 references, including ignored files excluded from the initial search ---"
rg -n -uu "claude-opus-4-8|opus-4-8" . --glob '!node_modules' --glob '!dist' --glob '!build' || true
echo "--- focused test cases ---"
rg -n -A12 -B8 "claude-(sonnet-4-6|opus-4-6|opus-4-7)|1m|context window" tests/claude-agents-inject.test.tsRepository: lidge-jun/opencodex
Length of output: 50375
🌐 Web query:
Anthropic Claude Opus 4.8 context window official documentation model identifier
💡 Result:
The Anthropic Claude Opus 4.8 model features a 1,000,000 (1M) token context window [1][2][3]. The official model identifier for use via the Anthropic API is claude-opus-4-8 [4][5]. As of August 2, 2026, Claude Opus 4.8 is a legacy model, having been succeeded by more recent generations such as the 5th-generation models (e.g., Claude Opus 5, Claude Sonnet 5, and Claude Fable 5) [6][7][8].
Citations:
- 1: https://platform.claude.com/docs/en/build-with-claude/context-windows
- 2: https://www.anthropic.com/claude/opus
- 3: https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-8.html
- 4: https://www.anthropic.com/news/claude-opus-4-8
- 5: https://docs.aimlapi.com/api-references/text-models-llm/anthropic/claude-4.8-opus
- 6: https://www.anthropic.com/news/claude-sonnet-5
- 7: https://www.anthropic.com/research/claude-opus-5
- 8: https://www.anthropic.com/claude/fable
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- authoritative project evidence for Claude Opus 4.8 ---"
sed -n '1,35p' devlog/_fin/260710_provider_hardening/001_research_frontier.md
sed -n '1,60p' devlog/_fin/260710_provider_hardening/020_wp2_anthropic_family.md
sed -n '1,80p' devlog/_plan/260802_wt3_provider_wire/030_bug_c_claude_1m_windows.md
echo "--- registry model metadata tests and static-map consumers ---"
rg -n -S "modelContextWindows|ANTHROPIC_MODEL_CONTEXT_WINDOWS|claude-opus-4-8" tests src/codex src/providers --glob '*.ts' --glob '*.tsx' | head -160
sed -n '80,145p' src/claude/context-windows.ts
sed -n '1,115p' src/codex/catalog/provider-fetch.ts
echo "--- focused test file around Anthropic agent coverage ---"
sed -n '45,72p' tests/claude-agents-inject.test.tsRepository: lidge-jun/opencodex
Length of output: 38621
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
registry = Path("src/providers/registry.ts").read_text()
metadata = Path("src/generated/jawcode-model-metadata.ts").read_text()
research = Path("devlog/_fin/260710_provider_hardening/001_research_frontier.md").read_text()
m = re.search(r'ANTHROPIC_MODEL_CONTEXT_WINDOWS[^=]*=\s*\{([^}]*)\}', registry)
assert m, "Anthropic context-window map not found"
entries = dict(re.findall(r'"([^"]+)":\s*([0-9_]+)', m.group(1)))
print("registry claude-opus-4-8:", entries.get("claude-opus-4-8"))
assert entries.get("claude-opus-4-8") == "1_000_000"
assert re.search(r'claude-opus-4-8\s*\|\s*1,000,000', research)
assert re.search(r'"anthropic\.claude-opus-4-8",1000000', metadata)
print("project evidence agrees: claude-opus-4-8 is a 1M model")
test_text = Path("tests/claude-agents-inject.test.ts").read_text()
print("direct agent-injection assertion:", "claude-opus-4-8" in test_text)
PYRepository: lidge-jun/opencodex
Length of output: 288
Add claude-opus-4-8 generated-profile coverage.
src/providers/registry.ts:227 correctly assigns a 1M context window. Extend tests/claude-agents-inject.test.ts:55-67 with anthropic/claude-opus-4-8 and expect claude-opus-4-8[1m]. The existing registry assertion does not cover the generated-profile marker path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/registry.ts` at line 227, Extend the generated-profile coverage
in the claude-agents injection test to include anthropic/claude-opus-4-8 and
assert the generated marker claude-opus-4-8[1m], while preserving the existing
registry context-window assertion.
Source: Path instructions
| test("a DeepSeek continuation keeps reasoning_text", () => { | ||
| // Mirror the runtime flow: saved configs carry no registry-only flags; the | ||
| // enrich backfill supplies them before the adapter serializes. | ||
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("deepseek")!), apiKey: "sk-test" }; | ||
| enrichProviderFromRegistry("deepseek", provider); | ||
| const body = buildBody(provider); | ||
| const item = (body.input as Record<string, unknown>[])[0]!; | ||
| expect(item.content).toEqual([{ type: "reasoning_text", text: "think step by step" }]); | ||
| }); | ||
|
|
||
| test("a canonical OpenAI provider still blanks reasoning content", () => { | ||
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("openai-apikey")!), apiKey: "sk-test" }; | ||
| const body = buildBody(provider); | ||
| const item = (body.input as Record<string, unknown>[])[0]!; | ||
| expect(item.content).toEqual([]); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
Add explicit opt-out coverage for the reasoning-preservation flag.
The tests cover DeepSeek registry backfill and OpenAI default blanking. They do not prove that an explicit preserveResponsesReasoningContent: false remains authoritative on DeepSeek.
Add a test that enriches a DeepSeek provider with this explicit value and asserts that reasoning content is blanked.
As per path instructions, keep this focused regression in the flat tests/ suite near the existing Responses tests.
Suggested regression
+ test("an explicit opt-out blanks DeepSeek reasoning content", () => {
+ const provider = {
+ ...providerConfigSeed(getProviderRegistryEntry("deepseek")!),
+ apiKey: "sk-test",
+ preserveResponsesReasoningContent: false,
+ };
+ enrichProviderFromRegistry("deepseek", provider);
+ const body = buildBody(provider);
+ const item = (body.input as Record<string, unknown>[])[0]!;
+ expect(item.content).toEqual([]);
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test("a DeepSeek continuation keeps reasoning_text", () => { | |
| // Mirror the runtime flow: saved configs carry no registry-only flags; the | |
| // enrich backfill supplies them before the adapter serializes. | |
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("deepseek")!), apiKey: "sk-test" }; | |
| enrichProviderFromRegistry("deepseek", provider); | |
| const body = buildBody(provider); | |
| const item = (body.input as Record<string, unknown>[])[0]!; | |
| expect(item.content).toEqual([{ type: "reasoning_text", text: "think step by step" }]); | |
| }); | |
| test("a canonical OpenAI provider still blanks reasoning content", () => { | |
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("openai-apikey")!), apiKey: "sk-test" }; | |
| const body = buildBody(provider); | |
| const item = (body.input as Record<string, unknown>[])[0]!; | |
| expect(item.content).toEqual([]); | |
| }); | |
| test("a DeepSeek continuation keeps reasoning_text", () => { | |
| // Mirror the runtime flow: saved configs carry no registry-only flags; the | |
| // enrich backfill supplies them before the adapter serializes. | |
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("deepseek")!), apiKey: "sk-test" }; | |
| enrichProviderFromRegistry("deepseek", provider); | |
| const body = buildBody(provider); | |
| const item = (body.input as Record<string, unknown>[])[0]!; | |
| expect(item.content).toEqual([{ type: "reasoning_text", text: "think step by step" }]); | |
| }); | |
| test("an explicit opt-out blanks DeepSeek reasoning content", () => { | |
| const provider = { | |
| ...providerConfigSeed(getProviderRegistryEntry("deepseek")!), | |
| apiKey: "sk-test", | |
| preserveResponsesReasoningContent: false, | |
| }; | |
| enrichProviderFromRegistry("deepseek", provider); | |
| const body = buildBody(provider); | |
| const item = (body.input as Record<string, unknown>[])[0]!; | |
| expect(item.content).toEqual([]); | |
| }); | |
| test("a canonical OpenAI provider still blanks reasoning content", () => { | |
| const provider = { ...providerConfigSeed(getProviderRegistryEntry("openai-apikey")!), apiKey: "sk-test" }; | |
| const body = buildBody(provider); | |
| const item = (body.input as Record<string, unknown>[])[0]!; | |
| expect(item.content).toEqual([]); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/deepseek-reasoning-replay.test.ts` around lines 69 - 84, Add a focused
test beside the existing DeepSeek and OpenAI tests in the flat tests suite that
creates a DeepSeek provider with preserveResponsesReasoningContent explicitly
set to false, runs enrichProviderFromRegistry and buildBody, and asserts the
resulting reasoning content is blank. Preserve the existing registry-backfill
and OpenAI coverage.
Source: Path instructions
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b30283b6d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| ### Bug A — PR #850: browser-extension CORS origins all collapse to `null` | ||
|
|
||
| - Root cause: `URL.origin` serializes `chrome-extension://...` (and other non-special schemes) as the string `"null"` per the WHATWG URL Standard. Comparing that value makes every browser extension origin look identical — the allowlist cannot distinguish the configured extension from any other, while rejecting `*` leaves users no safe path. |
There was a problem hiding this comment.
Remove unfixed security research from the tracked devlog
This file publicly identifies an unfixed CORS origin-confusion weakness, its root cause, affected request paths, severity, and fix before this commit changes auth-cors.ts; pushing the commit therefore discloses actionable pre-release security material. Remove these notes from the commit/history and keep them in .tmp/ until the fix has shipped.
AGENTS.md reference: AGENTS.md:L61-L68
Useful? React with 👍 / 👎.
| modelWireDefaults: { | ||
| "gpt-5.3-codex": "openai-responses", |
There was a problem hiding this comment.
Route Copilot Responses models to the unversioned endpoint
When any of these newly mapped models is selected, the Copilot provider still has no responsesPath, so the key-auth Responses adapter's fallback in openai-responses.ts constructs https://api.githubcopilot.com/v1/responses; Copilot's API uses the unversioned /responses route, matching its existing /chat/completions convention. Consequently all seven defaults are routed away from chat but still fail at the upstream URL. Set responsesPath: "/responses" on this registry entry and assert the exact captured URL—the current toContain("/responses") assertion accepts the erroneous /v1/responses URL.
Useful? React with 👍 / 👎.
| } | ||
| parsed.options.serviceTier = tier; | ||
| } | ||
| applyServiceTierGate(route.provider, parsed._rawBody, parsed.options); |
There was a problem hiding this comment.
Clear the copied service tier after gating
When a client supplies service_tier: "priority" to DeepSeek or another unsupported Responses provider, logCtx.requestedServiceTier and requestedSpeedLabel have already been copied from parsed.options at lines 1263-1264 before this gate clears only the raw body and parsed options. The completed request log therefore still reports a fast request, and effectiveServiceTier can use that stale value when calculating dashboard and usage-summary costs even though no tier reached the upstream. Move the logging assignment after final-route normalization or clear the corresponding logCtx fields when the gate strips the tier.
Useful? React with 👍 / 👎.
|
Superseded by #892, which merges this branch and lands all three fixes: Copilot mixed-wire routing (Responses-only models off chat completions), the DeepSeek service_tier capability gate in its final tri-state form (true permits injection, false strips, undefined preserves caller values — the docs were aligned in five locales), reasoning_content replay for DeepSeek tool-call continuations, and Claude 4.6/4.7 1M windows with [1m] picker rows restricted to authoritative >=1M windows (the 372K-route marking is gone from the picker too). Thank you — the reports and the implementation drove the fix. |
Summary
Three confirmed must-fix provider-wire bugs, one consolidated branch (wt3 lane of the bugfix campaign; unit docs in
devlog/_plan/260802_wt3_provider_wire/). Each bug landed as its own PABCD cycle with an independent audit gate.1. Copilot mixed-wire routing (closes #748, supersedes the routing half of #746)
The
github-copilotpreset's provider-wideopenai-chatadapter breaks Responses-only models:gpt-5.4rejects/chat/completionsfor real Codex-agent traffic (function tools + reasoning), verified externally (litellm#23332, JetBrains LLM-29711, #748 field runs). The fix declares seven verified models (gpt-5.3-codex,gpt-5.4,gpt-5.4-mini,gpt-5.5,gpt-5.6-luna,gpt-5.6-sol,gpt-5.6-terra) as registrymodelWireDefaults— the precedence mechanism already on the tree (hard pin → explicitmodelAdapters→ registry default → provider adapter). Chat-served models are untouched;gpt-5.4-nanostays out (no field report) as a documentedmodelAdaptersopt-in. #746's sampling/credential-replay parts are NOT included here — they remain a separate parity/security unit.2. DeepSeek:
service_tiercapability gate + reasoning replay (supersedes #860; local half of #875)service_tieris an OpenAI-only Responses parameter, but fast mode injected it for every Responses provider. A provider-levelsupportsServiceTiercapability now gates it after the final route is settled, adopting fix(responses): gate service tiers by provider capability #860's reviewed fail-closed semantics: canonicalopenai/openai-apikeykeep fast-mode behavior,deepseek/volcengine-agent-planstrip, unclassified providers fail closed unless explicitly opted in. Stripping also clearsoptions.serviceTierso logging never mislabels a removed tier.sanitizeReasoningInputContent()blanked reasoningcontentfor EVERY Responses provider — a rule only the ChatGPT native backend needs. DeepSeek's Responses API accepts plaintext reasoning replay, so providers flaggedpreserveResponsesReasoningContentkeep it (ocxr1envelopes still stripped). [Bug] DeepSeek V4 Flash Responses route stalls after tool calls #875 stays open: the "no follow-up request sent at all" observation has no local explanation (triage comment posted).enrichProviderFromRegistry+ router backfill supply them without overriding explicit config.3. Claude 4.6/4.7 1M context windows (supersedes #839 and #854 as one fix)
ANTHROPIC_MODEL_CONTEXT_WINDOWSomittedclaude-opus-4-7,claude-opus-4-6,claude-sonnet-4-6— they advertisedmax_input_tokens: null, emitted no[1m]picker row, and Claude Code accounted them at 200k. All three are documented at 1M by Anthropic (2026-02-05 / 2026-04-16 / 2026-02-17; no beta header required per the official context-window contract). Also ports #854's second half: generated subagent defs now mark[1m]only on authoritative ≥1M windows (a 372K route was previously written[1m]with no compaction pairing), preserving genuine routed[1m]ids and provider caps. Generated jawcode metadata and the stale generator override are aligned.Tests
tests/github-copilot-wire-defaults.test.ts(22),tests/service-tier-capability.test.ts(12),tests/deepseek-reasoning-replay.test.ts(6) — incl. captured-upstream-URL and livehandleResponsespayload proof.tests/claude-agents-inject.test.ts(372k contract + 5 regressions),tests/codex-catalog.test.ts(1M contract),tests/provider-registry-parity.test.ts(all three windows).bun run typecheckexit 0,bun run test7043 pass / 0 fail (484 files, verified on a clean clone on a separate host),bun run privacy:scanpassed.Docs
docs-sitereference (supportsServiceTier,preserveResponsesReasoningContent, updatedmodelAdaptersrow) and guides (Copilot routing precedence, capability-gated fast tier) in EN + ko/ja/zh-cn/ru.Base note
Local
devis currently ahead oforigin/dev(parallel sessions' unpushed commits); this branch is based on localdevtip, so the PR diff includes those commits untildevis pushed. The 15 wt3 commits are the tip of the branch.🤖 Generated with Codex
Summary by CodeRabbit
New Features
Documentation
Bug Fixes